/**
 * Copyright (c) 2020 - present, Inspur Genersoft Co., Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License. 
 */
import { DateObject, MarkedDates, MarkStatus, MonthTag, weekDays } from '../types/common';
import { CalendarWeekItem, CalenderDayItem } from "../types/calendar";
import { useDate } from './use-date';
import { useMark } from './use-mark';
import { useMonth } from './use-month';
import { UseCalendar, UseDisableDate } from './types';
import { useNumber } from './use-number';

export default function useCalendar({ isDisabledDate }: UseDisableDate): UseCalendar {

    const { getToday } = useDate();
    const { daysInMonth, daysInPreMonth, getNextMonth, getPreviousMonth } = useMonth();
    const { isHighlightedDate, isMarkedDate } = useMark();
    const { getWeekNumber } = useNumber();

    function getSundayIndex(firstDayOfWeek: string): number {
        // Index of Sunday day
        const dayIdx = weekDays.indexOf(firstDayOfWeek);
        return dayIdx > 0 ? 7 - dayIdx : 0;
    }

    function getMonthStartIndex(year: number, month: number, sundayIndex: number): number {
        // Month start index
        const date: Date = new Date();
        date.setDate(1);
        date.setMonth(month - 1);
        date.setFullYear(year);
        const index = date.getDay() + sundayIndex;
        return index >= 7 ? index - 7 : index;
    }

    function isCurrentDay(d: number, m: number, y: number, tag: number, today: DateObject): boolean {
        // Check is a given date the today
        return d === today.day && m === today.month && y === today.year && tag === MonthTag.current;
    }

    function generateCalendar(
        month: number,
        year: number,
        firstDayOfWeek: string,
        markedDates: MarkedDates[],
        markWeekends: MarkStatus,
        highlightDates: DateObject[],
        highlightSaturday: boolean,
        highlightSunday: boolean,
        showWeekNumbers: boolean
    ): CalendarWeekItem[] {

        const dates: CalendarWeekItem[] = [];
        const today: DateObject = getToday();
        const monthStart: number = getMonthStartIndex(year, month, getSundayIndex(firstDayOfWeek));
        const daysInCurrentMonth: number = daysInMonth(month, year);
        const theDaysInPreMonth: number = daysInPreMonth(month, year);

        let day = 1;
        let tag: MonthTag = MonthTag.previous;
        for (let dayIndexInWeek = 1; dayIndexInWeek < 7; dayIndexInWeek++) {
            const days: CalenderDayItem[] = [];
            if (dayIndexInWeek === 1) {
                // First week
                const startIndexToDisplayInPreMonth = theDaysInPreMonth - monthStart + 1;
                // Previous month
                for (let dayIndexInPreMonth = startIndexToDisplayInPreMonth;
                    dayIndexInPreMonth <= theDaysInPreMonth; dayIndexInPreMonth++
                ) {
                    const date: DateObject = {
                        year: month === 1 ? year - 1 : year,
                        month: month === 1 ? 12 : month - 1,
                        day: dayIndexInPreMonth
                    };
                    const isCurrent = isCurrentDay(dayIndexInPreMonth, month, year, tag, today);
                    const disable = isDisabledDate(date);
                    const marked = isMarkedDate(date, markedDates, markWeekends);
                    const highlight = isHighlightedDate(date, highlightSunday, highlightSaturday, highlightDates);
                    days.push({ date, tag, isCurrent, disable, marked, highlight });
                }

                tag = MonthTag.current;
                // Current month
                const daysToDispalyInFirstWeek: number = 7 - days.length;
                for (let dayIndex = 0; dayIndex < daysToDispalyInFirstWeek; dayIndex++) {
                    const date: DateObject = { year, month, day };
                    const isCurrent = isCurrentDay(day, month, year, tag, today);
                    const disable = isDisabledDate(date);
                    const marked = isMarkedDate(date, markedDates, markWeekends);
                    const highlight = isHighlightedDate(date, highlightSunday, highlightSaturday, highlightDates);
                    days.push({ date, tag, isCurrent, disable, marked, highlight });
                    day++;
                }
            } else {
                // Rest of the weeks
                for (let dayIndexInWeek = 1; dayIndexInWeek <= 7; dayIndexInWeek++) {
                    if (day > daysInCurrentMonth) {
                        // Next month
                        day = 1;
                        tag = MonthTag.next;
                    }
                    const date: DateObject = {
                        year: tag === MonthTag.next && month === 12 ? year + 1 : year,
                        month: tag === MonthTag.current ? month : tag === MonthTag.next && month < 12 ? month + 1 : 1,
                        day
                    };
                    const isCurrent = isCurrentDay(day, month, year, tag, today);
                    const disable = isDisabledDate(date);
                    const marked = isMarkedDate(date, markedDates, markWeekends);
                    const highlight = isHighlightedDate(date, highlightSunday, highlightSaturday, highlightDates);
                    days.push({ date, tag, isCurrent, disable, marked, highlight });
                    day++;
                }
            }
            const numberInTheYear: number = showWeekNumbers && firstDayOfWeek === 'Mon.' ? getWeekNumber(days[0].date) : 0;
            dates.push({ days, numberInTheYear, year });
        }
        return dates;
    }

    return { generateCalendar };
}
